use bevy::prelude::*;
use rand::prelude::*;

const SNAKE_HEAD_COLOR: Color = Color::rgb(0.7, 0.7, 0.7);

#[derive(Component)]
struct SnakeHead {
    direction: Direction,
}

// 11 步：让蛇自动前进
#[derive(Resource)]
struct BTimer(Timer);

#[derive(PartialEq, Clone, Copy)]
enum Direction {
    Left,
    Right,
    Up,
    Down,
}

impl Direction {
    fn opposite(self) -> Self {
        match self {
            Self::Left => Self::Right,
            Self::Right => Self::Left,
            Self::Down => Self::Up,
            Self::Up => Self::Down,
        }
    }
}

// 第12步：定义蛇身
#[derive(Component)]
struct SnakeSegment;

#[derive(Resource, Default, Deref, DerefMut)]
struct SnakeSegments(Vec<Entity>);

// 6. 将窗口网格化
const ARENA_WIDTH: u32 = 25;
const ARENA_HEIGHT: u32 = 25;

// 这一步，我们添加了 Position 和 Size 两种 Components。用来控制蛇头的逻辑位置和显示大小。
#[derive(Component, Clone, Copy, PartialEq, Eq)]
struct Position {
    x: i32,
    y: i32,
}

#[derive(Component)]
struct Size {
    width: f32,
    height: f32,
}

impl Size {
    pub fn square(x: f32) -> Self {
        Self {
            width: x,
            height: x,
        }
    }
}

// 14. 边吃边长大
// 吃食物的原理就是当蛇头占据了那个食物的位置时，就在系统中注销掉那个食物，然后在蛇身的尾巴位置处添加一个小方块
#[derive(Event)]
struct GrowthEvent;

#[derive(Resource, Default)]
struct LastTailPosition(Option<Position>);

// 15. GameOver事件
#[derive(Event)]
struct GameOverEvent;

// 14.1 处理蛇吃食物的system
fn snake_eating(
    mut commands: Commands,
    mut growth_writer: EventWriter<GrowthEvent>,
    food_positions: Query<(Entity, &Position), With<Food>>,
    head_positions: Query<&Position, With<SnakeHead>>,
) {
    for head_pos in head_positions.iter() {
        for (ent, food_pos) in food_positions.iter() {
            // 通过遍历来判断有没有吃到食物
            if head_pos == food_pos {
                // 摧毁吃到食物实体
                commands.entity(ent).despawn();
                // 发送GrowthEvent事件
                growth_writer.send(GrowthEvent);
            }
        }
    }
}

// 14.2 处理蛇长大的system
fn snake_growth(
    commands: Commands,
    last_tail_position: Res<LastTailPosition>,
    mut segments: ResMut<SnakeSegments>,
    mut growth_reader: EventReader<GrowthEvent>,
) {
    // 通过事件机制来解耦蛇长大的逻辑
    if growth_reader.read().next().is_some() {
        segments.push(spawn_segment(commands, last_tail_position.0.unwrap()))
    }
}

// 2. 设置2d Camera
// 由于引擎本身是一个托管系统（带主循环的 Runtime），我们要在这个引擎所维护的世界状态里添加（或删除）新的东西，
// 必须使用 Commands 这种任务指令形式。你可以把它想象成总线或消息队列编程模型
fn setup_camera(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());
}

// 3. 创建蛇头
// 12步时改用SnakeSegment来维护蛇的结构
fn spawn_snake(mut commands: Commands, mut segments: ResMut<SnakeSegments>) {
    *segments = SnakeSegments(vec![
        commands
            // 生成蛇头
            .spawn(SpriteBundle {
                sprite: Sprite {
                    color: SNAKE_HEAD_COLOR,
                    ..default()
                },
                transform: Transform {
                    scale: Vec3::new(10.0, 10.0, 10.0),
                    ..default()
                },
                ..default()
            })
            .insert(SnakeHead {
                direction: Direction::Up,
            })
            .insert(SnakeSegment) // 蛇头是特殊的蛇身
            .insert(Position { x: 3, y: 3 })
            .insert(Size::square(0.8))
            .id(),
        // 生成蛇身
        spawn_segment(commands, Position { x: 3, y: 2 }),
    ]);
}

fn spawn_segment(mut commands: Commands, position: Position) -> Entity {
    commands
        .spawn(SpriteBundle {
            sprite: Sprite {
                color: SNAKE_HEAD_COLOR,
                ..default()
            },
            ..default()
        })
        .insert(SnakeSegment)
        .insert(position)
        .insert(Size::square(0.65))
        .id()
}

// 根据用户键盘行为，预处理蛇的前进方向
fn snake_movement_input(keyboard_input: Res<Input<KeyCode>>, mut heads: Query<&mut SnakeHead>) {
    if let Some(mut head) = heads.iter_mut().next() {
        let dir: Direction = if keyboard_input.just_pressed(KeyCode::Left) {
            Direction::Left
        } else if keyboard_input.just_pressed(KeyCode::Right) {
            Direction::Right
        } else if keyboard_input.just_pressed(KeyCode::Up) {
            Direction::Up
        } else if keyboard_input.just_pressed(KeyCode::Down) {
            Direction::Down
        } else {
            head.direction
        };

        if dir != head.direction.opposite() {
            head.direction = dir;
        }
    }
}

// 4. 让蛇移动
// 请注意参数是 Query<(&SnakeHead, &mut Transform)> 类型，
// 它的意思是从世界状态中去查找同时拥有 SnakeHead、Transform 两种 Components 的 entity，
// 它定义了一个迭代器，并且 Transform 的实例还是可以修改的
// 5. 控制蛇的有移动方向
// 7. 让蛇按照网格移动
//13. 让蛇身跟着蛇的头一起运动：其实就是让蛇身的每一节跟着蛇头的移动一起变换坐标就行了
fn snake_movement(
    time: Res<Time>,
    mut timer: ResMut<BTimer>,
    segments: ResMut<SnakeSegments>,
    mut heads: Query<(Entity, &SnakeHead)>,
    mut positions: Query<&mut Position>,
    mut last_tail_position: ResMut<LastTailPosition>,
    mut game_over_writer: EventWriter<GameOverEvent>,
) {
    // 如果时间未到 0.2s 就立即返回
    if !timer.0.tick(time.delta()).finished() {
        return;
    }

    if let Some((head_entity, head)) = heads.iter_mut().next() {
        let segment_positions = segments
            .iter()
            .map(|e| *positions.get_mut(*e).unwrap())
            .collect::<Vec<Position>>();
        // 处理蛇头的移动
        let mut head_pos = positions.get_mut(head_entity).unwrap();
        match &head.direction {
            Direction::Left => {
                head_pos.x -= 1;
            }
            Direction::Right => {
                head_pos.x += 1;
            }
            Direction::Up => {
                head_pos.y += 1;
            }
            Direction::Down => {
                head_pos.y -= 1;
            }
        };

        // ----------- 游戏GameOver相关逻辑判断 -------------
        // 撞墙逻辑判断
        if head_pos.x < 0
            || head_pos.y < 0
            || head_pos.x as u32 >= ARENA_WIDTH
            || head_pos.y as u32 >= ARENA_HEIGHT
        {
            game_over_writer.send(GameOverEvent); // 发送GameOver事件
        }
        // 撞到自身逻辑判断
        if segment_positions.contains(&head_pos) {
            game_over_writer.send(GameOverEvent); // 发送GameOver事件
        }
        // ------------------------------------------------

        // 处理蛇身每一段位置变化
        segment_positions
            .iter()
            .zip(segments.iter().skip(1))
            .for_each(|(pos, segment)| {
                *positions.get_mut(*segment).unwrap() = *pos;
            });

        *last_tail_position = LastTailPosition(Some(*segment_positions.last().unwrap()));
    }
}

// 6.1 计算方块元素的大小
// 新增了size_scaling和position_translation两个system，用来在每一帧计算蛇头的尺寸和位置。
fn size_scaling(
    primary_query: Query<&Window, With<bevy::window::PrimaryWindow>>,
    mut q: Query<(&Size, &mut Transform)>,
) {
    let window = primary_query.get_single().unwrap();
    for (sprite_size, mut transform) in q.iter_mut() {
        transform.scale = Vec3::new(
            sprite_size.width / ARENA_WIDTH as f32 * window.width() as f32,
            sprite_size.height / ARENA_HEIGHT as f32 * window.height() as f32,
            1.0,
        );
    }
}

// 6.2 计算位移
fn position_translation(
    primary_query: Query<&Window, With<bevy::window::PrimaryWindow>>,
    mut q: Query<(&Position, &mut Transform)>,
) {
    fn convert(pos: f32, bound_window: f32, bound_game: f32) -> f32 {
        let tile_size = bound_window / bound_game;
        pos / bound_game * bound_window - (bound_window / 2.) + (tile_size / 2.)
    }

    let window = primary_query.get_single().unwrap();
    for (pos, mut transform) in q.iter_mut() {
        transform.translation = Vec3::new(
            convert(pos.x as f32, window.width() as f32, ARENA_WIDTH as f32),
            convert(pos.y as f32, window.height() as f32, ARENA_HEIGHT as f32),
            0.0,
        );
    }
}

// 9. 随机产生食物
const FOOD_COLOR: Color = Color::rgb(1.0, 0.0, 1.0);

#[derive(Component)]
struct Food;

// 10. 使用定时器产生食物
// 我们要控制食物的产生速度，比如 2 秒产生一颗食物
// Timer 类型是 Bevy 提供的定时器类型，我们用 newtype 模式定义一个自己的定时器
#[derive(Resource)]
struct FoodSpawnTimer(Timer);

// 用 ResMut<FoodSpawnTimer> 这种形式来使用资源
// 用 Res<Time> 这种形式来获取游戏中的时间，这个也是 Bevy 引擎提供的
fn food_spawner(
    mut commands: Commands,
    time: Res<Time>,
    mut timer: ResMut<FoodSpawnTimer>,
    segment_pos_set: Query<&Position, With<SnakeSegment>>,
) {
    // 如果时间未到两秒就立刻返回
    if !timer.0.tick(time.delta()).finished() {
        return;
    }

    // let rand_x: i32 = (50.0f32 * ARENA_WIDTH as f32) as i32;
    // let rand_y: i32 = (50.0f32 * ARENA_HEIGHT as f32) as i32;
    let rand_x: i32 = (random::<f32>() * ARENA_WIDTH as f32) as i32;
    let rand_y: i32 = (random::<f32>() * ARENA_HEIGHT as f32) as i32;

    for pos in segment_pos_set.iter() {
        if pos.x == rand_x && pos.y == rand_y {
            return;
        }
    }

    commands
        .spawn(SpriteBundle {
            sprite: Sprite {
                color: FOOD_COLOR,
                ..default()
            },
            ..default()
        })
        .insert(Food)
        .insert(Position {
            x: rand_x,
            y: rand_y,
        })
        .insert(Size::square(0.8));
}

// 处理game over事件的system
fn game_over(
    mut commands: Commands,
    mut reader: EventReader<GameOverEvent>,
    food: Query<Entity, With<Food>>,
    segments: Query<Entity, With<SnakeSegment>>,
    segments_res: ResMut<SnakeSegments>,
) {
    if reader.read().next().is_some() {
        for ent in food.iter().chain(segments.iter()) {
            commands.entity(ent).despawn();
        }
        spawn_snake(commands, segments_res)
    }
}

fn main() {
    App::new()
        // 1. 加入默认 Plugin 集合，里面有个主事件循环，还有个创建窗口的功能
        // .add_plugins(DefaultPlugins)
        // 8. 修改窗口比例：默认打开的窗口是长方形的，我们要给它配置成方形
        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                // 设置窗口标题为 Snake!
                title: "Snake!".to_string(),
                // 设置窗口尺寸为 500px x 500px
                resolution: bevy::window::WindowResolution::new(500.0, 500.0),
                ..default()
            }),
            ..default()
        }))
        // 设置窗口填充背景颜色为 Color::rgb(0.04, 0.04, 0.04)
        .insert_resource(ClearColor(Color::rgb(0.04, 0.04, 0.04)))
        // / 插入定时器资源
        .insert_resource(FoodSpawnTimer(Timer::from_seconds(
            1.5,
            TimerMode::Repeating,
        )))
        .insert_resource(BTimer(Timer::from_seconds(0.2, TimerMode::Repeating)))
        // 插入蛇身，定义为资源
        .insert_resource(SnakeSegments::default())
        // 插入蛇尾位置，定义为资源
        .insert_resource(LastTailPosition::default())
        // 插入蛇身长大事件
        .add_event::<GrowthEvent>()
        // GameOver事件注册
        .add_event::<GameOverEvent>()
        // 设置 2D 的 Camera 和 添加蛇头
        .add_systems(Startup, (setup_camera, spawn_snake))
        // 更新Update模式下的system集
        .add_systems(
            Update,
            (
                // 明确指定 snake_movement_input 在 snake_movement system 之前处理
                snake_movement_input.before(snake_movement),
                snake_movement,
                snake_eating,
                snake_growth,
                size_scaling,
                position_translation,
            ),
        )
        .add_systems(Update, food_spawner)
        .add_systems(Update, game_over)
        .run();
}
